package io.traveller.auth.provider.service;

import io.traveller.auth.api.constants.AccountType;
import io.traveller.auth.api.dto.AccountTokenDTO;
import io.traveller.auth.api.service.AuthService;
import io.traveller.auth.provider.entity.*;
import io.traveller.auth.provider.mapper.*;
import io.traveller.common.mybatis.Field;
import io.traveller.common.util.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.*;
import org.springframework.util.CollectionUtils;

import java.util.*;

/**
 * 权限Service实现类
 *
 * Created by yunai on 16/9/18.
 */
public class AuthServiceImpl implements AuthService {

    @Autowired
    private AccountPasswordMapper accountPasswordMapper;
    @Autowired
    private AccountTokenMapper accountTokenMapper;
    @Autowired
    private AccountMapper accountMapper;
    @Autowired
    private ResourceMapper resourceMapper;
    @Autowired
    private RoleMapper roleMapper;
    @Autowired
    private RoleResourceMapper roleResourceMapper;
    @Autowired
    private AccountRoleMapper accountRoleMapper;

    private final PathMatcher pathMatcher = new AntPathMatcher();

    /**
     * url与roleId映射
     * <p>
     * key1: serviceSys
     * key2: requestMethod
     * key3: requestURI
     * value: 角色编号Set
     */
    private Map<Integer, Map<String, Map<String, Set<Long>>>> urlRoleIdsMap;

    public void init() {
        initUrlRoleIdsMap();
    }

    private void initUrlRoleIdsMap() {
        // 加载角色和资源的关系
        Set<Long> roleIds = io.traveller.common.util.CollectionUtils.buildSet(roleMapper.selectRolesByStatus(Role.STATUS_ENABLE, new Field("id")),
                Long.class, "id");
        List<RoleResource> roleResources = roleResourceMapper.selectRoleResources(new Field("roleId", "resourceId"));
        Map<Long, Set<Long>> resourceIdRoleIdMap = new HashMap<>();
        for (RoleResource roleResource : roleResources) {
            if (!roleIds.contains(roleResource.getRoleId())) {
                continue;
            }
            if (!resourceIdRoleIdMap.containsKey(roleResource.getResourceId())) {
                resourceIdRoleIdMap.put(roleResource.getResourceId(), new HashSet<Long>());
            }
            resourceIdRoleIdMap.get(roleResource.getResourceId()).add(roleResource.getRoleId());
        }
        // 处理url与roleId的映射关系
        Map<Integer, Map<String, Map<String, Set<Long>>>> tempUrlRoleIdsMap = new HashMap<>();
        List<Resource> resources = resourceMapper.selectResourcesByTypesAndStatus(
                io.traveller.common.util.CollectionUtils.asSet(Resource.TYPE_MENU, Resource.TYPE_OPERATION),
                Resource.STATUS_ENABLE, new Field("id", "type", "serviceSys", "url", "method"));
        for (Resource resource : resources) {
            if (!StringUtils.hasText(resource.getUrl())) {
                continue;
            }
            if (resourceIdRoleIdMap.get(resource.getId()) == null) {
                continue;
            }
            if (resource.getMethod() != null) {
                resource.setMethod(resource.getMethod().toUpperCase());
            }
            if (!tempUrlRoleIdsMap.containsKey(resource.getServiceSys())) {
                tempUrlRoleIdsMap.put(resource.getServiceSys(), new HashMap<String, Map<String, Set<Long>>>());
            }
            if (!tempUrlRoleIdsMap.get(resource.getServiceSys()).containsKey(resource.getMethod())) {
                tempUrlRoleIdsMap.get(resource.getServiceSys()).put(resource.getMethod(), new HashMap<String, Set<Long>>());
            }
            if (!tempUrlRoleIdsMap.get(resource.getServiceSys()).get(resource.getMethod()).containsKey(resource.getUrl())) {
                tempUrlRoleIdsMap.get(resource.getServiceSys()).get(resource.getMethod()).put(resource.getUrl(), new HashSet<Long>());
            }
            tempUrlRoleIdsMap.get(resource.getServiceSys()).get(resource.getMethod()).get(resource.getUrl())
                    .addAll(resourceIdRoleIdMap.get(resource.getId())); // 如果菜单url和操作url重复，则合并2个可以使用的角色
        }
        // 赋值，直接将已经处理好的数据，切换内存里的变量
        this.urlRoleIdsMap = tempUrlRoleIdsMap;
    }

    @Override
    public AccountTokenDTO authorize(Integer accountSys, String username, String password) {
        // 查询账号
        AccountPassword accountPassword = accountPasswordMapper.selectAccountPasswordBySysAndUsernameAndPassword(accountSys, username, password,
                new Field("id", "accountId"));
        if (accountPassword == null) {
            return null;
        }
        // 账号禁用
//        if (AccountPassword.STATUS_DISABLE.equals(accountPassword.getStatus())) {
//            AccountTokenDTO dto = new AccountTokenDTO()
//        }
        // TODO 账号被禁用
        // TODO 登陆账号被禁用
        // 插入accessToken
        String accessToken = DigestUtils.md5DigestAsHex(UUID.randomUUID().toString().getBytes());
        String refreshToken = DigestUtils.md5DigestAsHex(UUID.randomUUID().toString().getBytes());
        Date createTime = new Date();
        Date expireTime = DateUtils.addDate(createTime, Calendar.DAY_OF_YEAR, 30); // TODO 使用时间是30天
        AccountToken accountToken = new AccountToken(accountPassword.getAccountId(), accountSys, createTime,
                AccountToken.STATUS_ENABLE, accessToken, refreshToken, expireTime);
        accountTokenMapper.insertAccountToken(accountToken);
        // 返回数据
        // TODO 账号状态
        Account account = accountMapper.selectAccount(accountPassword.getAccountId(), new Field("id", "userId"));
        return new AccountTokenDTO(accessToken, account.getId(), AccountType.USER, null, account.getUserId(),
                (int) (30 * DateUtils.DAY_LONG / DateUtils.SECOND_LONG));
    }

    @Override
    public AccountTokenDTO authorize(Integer accountSys, String refreshToken) {
        throw new UnsupportedOperationException("暂时不支持refreshToken认证方式");
    }

    @Override
    public AccountTokenDTO authorize0(Integer accountSys, String authorizationCode) {
        throw new UnsupportedOperationException("暂时不支持authorizationCode认证方式");
    }

    @Override
    public AccountTokenDTO authorize0(Integer accountSys, String clientId, String clientSecret) {
        throw new UnsupportedOperationException("暂时不支持clientId认证方式");
    }

    @Override
    public AccountTokenDTO findAccountToken(Integer accountSys, String accessToken) {
        AccountToken accountToken = accountTokenMapper.selectAccountTokenByAccessTokenAndSys(accessToken, accountSys,
                new Field("accountId", "createTime", "expireTime"));
        if (accountToken == null) {
            return null;
        }
        Account account = accountMapper.selectAccount(accountToken.getAccountId(), new Field("id",  "userId"));
        return new AccountTokenDTO(accessToken, account.getId(), AccountType.USER, null, account.getUserId(),
                DateUtils.diffSeconds(accountToken.getCreateTime(), accountToken.getExpireTime()));
    }

    @Override
    public boolean hasPermission(Long accountId, Integer accountType, Integer serviceSys,
                                 String requestURI, String requestMethod) {
        Map<String, Map<String, Set<Long>>> urlRolesMap1 = urlRoleIdsMap.get(serviceSys);
        if (CollectionUtils.isEmpty(urlRolesMap1)) { // serviceSys匹配为空，则不需要认证
            return true;
        }
        Map<String, Set<Long>> urlRolesMap2 = null;
        if (StringUtils.hasText(requestMethod)) {
            urlRolesMap2 = urlRolesMap1.get(requestMethod.toUpperCase());
        }
        if (urlRolesMap2 == null && urlRolesMap1.size() == 1) { // 当找不到配置，并且找不到时，使用*查找
            urlRolesMap2 = urlRolesMap1.get(Resource.METHOD_ALL);
        }
        if (CollectionUtils.isEmpty(urlRolesMap2)) { // requestMethod匹配为空，则不需要认证
            return true;
        }
        Set<Long> roleIds = null;
        for (Map.Entry<String, Set<Long>> entry : urlRolesMap2.entrySet()) { // TODO 不考虑多匹配成功
            if (pathMatcher.match(entry.getKey(), requestURI)) {
                roleIds = entry.getValue();
                break;
            }
        }
        if (CollectionUtils.isEmpty(roleIds)) { // requestURI匹配为空，则不需要认证
            return true;
        }
        List<AccountRole> accountRoles = accountRoleMapper.selectAccountRolesByAccountIdAndServiceSys(
                accountId, serviceSys, new Field("roleId"));
        for (AccountRole accountRole : accountRoles) {
            if (roleIds.contains(accountRole.getRoleId())) {
                return true;
            }
        }
        return false;
    }

}